<?php
/**
 * GuzzleHttp封装类（暂支持jsonAPI交互）
 *
 * @author dior <diaoxingkwan@vip.qq.com>
 *
 * @date 2021/05/12
 */

namespace app\common\util;

use app\common\exception\HttpRequestException;
use GuzzleHttp\Client;

class HttpRequest
{
    //请求返回数据格式设定
    private $responseSuccessCode = 1;
    private $responseCodeName = 'code';
    private $responseMsgName = 'msg';
    private $responseDataName = 'data';

    //是否校验错误
    private $isVerifyException;
    //请求基础配置
    private $config = [
        //超时时间
        'timeout' => 5,
    ];
    //其他配置
    private $options = [
        //是否校验ssl证书
        'verify' => false,
        'headers' => [],
    ];

    /**
     * HttpUtil constructor.
     * @param array $headers 请求头
     * @param bool $isVerifyException 是否校验异常
     */
    public function __construct(array $headers = [], bool $isVerifyException = true)
    {
        $this->options['headers'] = $headers;
        $this->isVerifyException = $isVerifyException;
    }

    /**
     * get
     * @param string $url 请求地址
     * @param array $data 请求带的参数
     * @param bool $isReturnAll 是否返回全部数据
     * @return mixed
     * @throws HttpRequestException
     */
    public function get(string $url, array $data = [], bool $isReturnAll = false)
    {
        if (!empty($data)) {
            $prefix = strstr($url, '?') ? '&' : '?';
            $url .= $prefix . http_build_query($data);
        }

        $this->config['base_uri'] = $url;
        $data = $this->send('GET')[$this->responseDataName];
        return $isReturnAll === true ? $data : $data[$this->responseDataName];
    }

    /**
     * post
     * @param string $url 请求地址
     * @param array $data 请求带的参数
     * @param bool $isReturnAll 是否返回全部数据
     * @return mixed
     * @throws HttpRequestException
     */
    public function post(string $url, array $data = [], bool $isReturnAll = false)
    {
        $this->config['base_uri'] = $url;
        $this->options['json'] = $data;

        $data = $this->send('POST');
        return $isReturnAll === true ? $data : $data[$this->responseDataName];
    }

    /**
     * put
     * @param string $url 请求地址
     * @param array $data 请求带的参数
     * @param bool $isReturnAll 是否返回全部数据
     * @return mixed
     * @throws HttpRequestException
     */
    public function put(string $url, array $data = [], bool $isReturnAll = false)
    {
        $this->config['base_uri'] = $url;
        $this->options['json'] = $data;

        $data = $this->send('PUT');
        return $isReturnAll === true ? $data : $data[$this->responseDataName];
    }

    /**
     * delete
     * @param string $url 请求地址
     * @param array $data 请求带的参数
     * @param bool $isReturnAll 是否返回全部数据
     * @return mixed
     * @throws HttpRequestException
     */
    public function delete(string $url, array $data = [], bool $isReturnAll = false)
    {
        $this->config['base_uri'] = $url;
        $this->options['json'] = $data;

        $data = $this->send('DELETE');
        return $isReturnAll === true ? $data : $data[$this->responseDataName];
    }

    /**
     * 执行发送
     * @param string $method
     * @return array
     * @throws HttpRequestException
     */
    private function send(string $method): array
    {
        //执行请求
        $client = new Client($this->config);
        $response = $client->request($method, '', $this->options);

        //statusCode验证
        if ($response->getStatusCode() !== 200) {
            $message = '请求异常，statusCode：' . $response->getStatusCode() . '，body：' . (string)$response->getBody();
            throw new HttpRequestException($message);
        }
        $body = (string)$response->getBody();
        if (empty($body)) {
            throw new HttpRequestException('请求异常(响应数据为空)');
        }
        //获取返回数据
        $responseData = json_decode($body, true);
        if (!is_array($responseData)) {
            throw new HttpRequestException('请求异常(响应数据格式异常)');
        }
        //是否校验
        $this->hasException($responseData);

        //返回数据
        return $responseData;
    }

    /**
     * 设置请求超时时间
     * @param int $timeout X秒
     * @return $this
     */
    public function setTimeout(int $timeout): HttpRequest
    {
        $this->config['timeout'] = $timeout;
        return $this;
    }

    /**
     * 设置请求头
     * @param array $headers
     * @return HttpRequest
     */
    public function setHeaders(array $headers): HttpRequest
    {
        $this->options['headers'] = array_merge($this->options['headers'], $headers);
        return $this;
    }

    /**
     * 是否校验返回的数据
     * @param bool $isVerifyException
     * @return $this
     */
    public function setIsVerifyException(bool $isVerifyException): HttpRequest
    {
        $this->isVerifyException = $isVerifyException;
        return $this;
    }

    /**
     * 是否校验证书
     * @param bool $isVerifySsl
     * @return $this
     */
    public function setIsVerifySsl(bool $isVerifySsl): HttpRequest
    {
        $this->options['verify'] = $isVerifySsl;
        return $this;
    }

    /**
     * 设置基础配置
     * @param array $config
     * @return $this
     */
    public function setConfig(array $config): HttpRequest
    {
        $this->config = array_merge($this->config, $config);
        return $this;
    }

    /**
     * 设置响应的数据格式
     * @param string $code 状态码
     * @param string $msg 消息内容
     * @param string $data 数据
     * @return $this
     */
    public function setResponseFormat(string $code = 'code', string $msg = 'msg', string $data = 'data'): HttpRequest
    {
        $this->responseCodeName = $code;
        $this->responseMsgName = $msg;
        $this->responseDataName = $data;
        return $this;
    }

    /**
     * 设置响应的成功码
     * @param int $code 状态码
     * @return $this
     */
    public function setResponseSuccessCode(int $code): HttpRequest
    {
        $this->responseSuccessCode = $code;
        return $this;
    }

    /**
     * 请求是否有异常
     * @param array $jsonData
     * @throws HttpRequestException
     */
    private function hasException(array $jsonData)
    {
        if ($this->isVerifyException !== true) {
            return;
        }
        //检测返回的状态码
        if (!array_key_exists($this->responseCodeName, $jsonData) || !is_numeric($jsonData[$this->responseCodeName])) {
            throw new HttpRequestException('返回数据中状态码格式错误：' . json_encode($jsonData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        }
        //检测返回的消息和数据
        if (!array_key_exists($this->responseMsgName, $jsonData) || !array_key_exists($this->responseDataName, $jsonData)) {
            throw new HttpRequestException('返回数据中消息或数据格式错误：' . json_encode($jsonData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        }
        //检测是否请求成功
        if (intval($jsonData[$this->responseCodeName]) !== $this->responseSuccessCode) {
            throw new HttpRequestException($jsonData[$this->responseMsgName] ?? '请求失败');
        }
    }
}